<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>系统架构Demo</title>
    <style>
      ::-webkit-scrollbar {
        display: none;
      }
      html,
      body {
        overflow: hidden;
        margin: 0;
      }
    </style>
  </head>
  <body>
    <div id="mountNode"></div>
    <div class="timeControllerOuterContainer">
      <div id="timeControllerContainer"></div>
    </div>
    <div class="detailPannel">
      <button id="btn_error">分析错误源节点</button>
      <div id="infoDetail"></div>
    </div>
    <div class="description">
      <p>
        Demo说明：
      </p>
      <p>
        本例用假数据展示了某次系统发生异常时，异常系统和其涉及系统的状态，其中发生异常的系统时红色的；
      </p>
      <p>
        系统间依赖关系用边表示，出现问题的依赖也对应映射成红色；
      </p>
      <p>
        标记出了疑似的问题源头系统，当点击右上角分析时，聚焦到该系统对应的节点上，并将系统近一小时的错误量作为扩展信息展示；
      </p>
      <p>
        时序分析：下方时时间轴，某个时间点系统出现的错误数量，点击选择某个时间点，对应关系图切换到选择时间对应的数据（这里因为时示例，仅模拟了两个时刻的数据）;
      </p>
    </div>
    <script>
      /*Fixing iframe window.innerHeight 0 issue in Safari*/ document.body.clientHeight;
    </script>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-3.1.1/build/g6.js"></script>
    <script src="https://cdn.bootcss.com/dagre/0.8.4/dagre.js"></script>
    <script src="http://momentjs.cn/downloads/moment.min.js"></script>
    <style>
      #mountNode {
        background: #001528;
      }
      .timeControllerOuterContainer {
        text-align: center;
        height: 100px;
        background-color: beige;
        position: relative;
        background: #001528;
      }
      #timeControllerContainer {
        position: absolute;
        background-color: #002242;
        position: absolute;
        bottom: 16px;
        left: 50%;
        text-align: center;
        transform: translate(-50%, 0%);
        padding-bottom: 10px;
        padding-top: 10px;
      }
      .g6-tooltip {
        border: 1px solid #e2e2e2;
        border-radius: 4px;
        font-size: 12px;
        color: #545454;
        background-color: rgba(255, 255, 255, 0.9);
        padding: 10px 8px;
        box-shadow: rgb(174, 174, 174) 0px 0px 10px;
      }
      .detailPannel {
        position: absolute;
        right: 64px;
        top: 32px;
      }
      #btn_error {
        color: white;
        background: #f75a15;
        border: 2px solid #f75a15;
        border-radius: 6px;
        cursor: pointer;
      }
      .description {
        position: absolute;
        left: 64px;
        top: 12px;
        color: white;
        width: 250px;
        font-size: 13px;
      }
    </style>
    <script>
      // ============================
      // 大图假数据 1
      const mockGraphData1 = {
        code: '0',
        data: {
          nodes: [
            {
              app: 'system1',
              abnormal: false,
              label: 'system1',
              id: 'system1',
            },
            {
              app: 'system2',
              abnormal: false,
              label: 'system2',
              id: 'system2',
            },
            {
              app: 'system3',
              abnormal: false,
              label: 'system3',
              id: 'system3',
            },
            {
              app: 'system4',
              abnormal: false,
              label: 'system4',
              id: 'system4',
            },
            {
              app: 'system5',
              abnormal: true,
              label: 'system5',
              id: 'system5',
            },
            {
              app: 'system6',
              abnormal: false,
              label: 'system6',
              id: 'system6',
            },
            {
              app: 'system7',
              abnormal: false,
              label: 'system7',
              id: 'system7',
            },
            {
              app: 'system8',
              abnormal: false,
              label: 'system8',
              id: 'system8',
            },
            {
              app: 'system9',
              abnormal: false,
              label: 'system9',
              id: 'system9',
            },
            {
              app: 'source_system',
              abnormal: true,
              label: 'source_system',
              id: 'source_system',
            },
            {
              app: 'system11',
              abnormal: false,
              label: 'system11',
              id: 'system11',
            },
            {
              app: 'system12',
              abnormal: false,
              label: 'system12',
              id: 'system12',
            },
            {
              app: 'system13',
              abnormal: true,
              label: 'system13',
              id: 'system13',
            },
            {
              app: 'sysX',
              abnormal: false,
              label: 'sysX',
              id: 'sysX',
            },
            {
              app: 'system15',
              abnormal: true,
              label: 'system15',
              id: 'system15',
            },
            {
              app: 'sysZ',
              abnormal: false,
              label: 'sysZ',
              id: 'sysZ',
            },
            {
              app: 'sysY',
              abnormal: false,
              label: 'sysY',
              id: 'sysY',
            },
          ],
          edges: [
            {
              abnormal: true,
              serviceList: [
                'com.abcd.efghi.jk.service.apple.pear.banana.rice.noodle.tea.drink',
                'com.abcd.efghi.jk.service.apple.pear.banana.rice.hi',
              ],
              source: 'system3',
              target: 'system13',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system6',
              target: 'system5',
            },
            {
              abnormal: true,
              serviceList: [
                'com.abcd.efghi.jk.service.apple.pear.banana.rice.fruit.eat.drink.findAll',
                'com.abcd.efghi.jk.service.apple.pear.banana.rice.fruit.eat.drink.findAllList',
              ],
              source: 'system7',
              target: 'source_system',
            },
            {
              abnormal: true,
              serviceList: [
                'com.abcd.efghi.jk.service.apple.pear.banana.rice.fruit.eat.drink.findAllList',
              ],
              source: 'source_system',
              target: 'source_system',
            },
            {
              abnormal: true,
              serviceList: ['com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen'],
              source: 'system5',
              target: 'source_system',
            },
            {
              abnormal: true,
              serviceList: ['com.abcd.efghi.jk.service.apple.pear.banana.playBall'],
              source: 'system12',
              target: 'source_system',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system7',
              target: 'system9',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system1',
              target: 'system11',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system9',
              target: 'system6',
            },
            {
              abnormal: true,
              serviceList: ['com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen'],
              source: 'system8',
              target: 'system5',
            },
            {
              abnormal: true,
              serviceList: [
                'com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen',
                'com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen',
              ],
              source: 'system8',
              target: 'source_system',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'sysX',
              target: 'system1',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system11',
              target: 'system8',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system7',
              target: 'system11',
            },
            {
              abnormal: true,
              serviceList: ['com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen'],
              source: 'system13',
              target: 'system15',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system9',
              target: 'system2',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'sysZ',
              target: 'sysY',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system4',
              target: 'sysX',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'sysY',
              target: 'system11',
            },
            {
              abnormal: true,
              serviceList: ['com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen'],
              source: 'system2',
              target: 'source_system',
            },
            {
              abnormal: true,
              serviceList: ['com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.cater'],
              source: 'system6',
              target: 'source_system',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system7',
              target: 'system12',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'sysZ',
              target: 'system7',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system7',
              target: 'system2',
            },
          ],
        },
        errorMsg: '',
        success: true,
        successMsg: '',
      };
      // ============================
      // 大图假数据 2
      const mockGraphData2 = {
        code: '0',
        data: {
          nodes: [
            {
              app: 'system1',
              abnormal: false,
              label: 'system1',
              id: 'system1',
            },
            {
              app: 'source_system',
              abnormal: true,
              label: 'source_system',
              id: 'source_system',
            },
            {
              app: 'system3',
              abnormal: false,
              label: 'system3',
              id: 'system3',
            },
            {
              app: 'system4',
              abnormal: false,
              label: 'system4',
              id: 'system4',
            },
            {
              app: 'system5',
              abnormal: false,
              label: 'system5',
              id: 'system5',
            },
            {
              app: 'system6',
              abnormal: false,
              label: 'system6',
              id: 'system6',
            },
            {
              app: 'system7',
              abnormal: false,
              label: 'system7',
              id: 'system7',
            },
          ],
          edges: [
            {
              abnormal: false,
              serviceList: [],
              source: 'system7',
              target: 'system6',
            },
            {
              abnormal: true,
              serviceList: ['com.abcd.ef.ghi.jklmn.service.abcd.efg.api.x1234.y12345'],
              source: 'system7',
              target: 'source_system',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system1',
              target: 'system7',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system5',
              target: 'system1',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system6',
              target: 'system3',
            },
            {
              abnormal: false,
              serviceList: [],
              source: 'system4',
              target: 'system5',
            },
          ],
        },
        errorMsg: '',
        success: true,
        successMsg: '',
      };
      // 节点详细假数据
      const appDetail = [
        {
          count: '0',
          time: '1570995300000',
          type: '0015',
        },
        {
          count: '2',
          time: '1570995360000',
          type: '0015',
        },
        {
          count: '3',
          time: '1570995420000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570995480000',
          type: '0015',
        },
        {
          count: '1',
          time: '1570995540000',
          type: '0015',
        },
        {
          count: '12',
          time: '1570995600000',
          type: '0015',
        },
        {
          count: '12',
          time: '1570995660000',
          type: '0015',
        },
        {
          count: '14',
          time: '1570995720000',
          type: '0015',
        },
        {
          count: '16',
          time: '1570995780000',
          type: '0015',
        },
        {
          count: '12',
          time: '1570995840000',
          type: '0015',
        },
        {
          count: '14',
          time: '1570995900000',
          type: '0015',
        },
        {
          count: '15',
          time: '1570995960000',
          type: '0015',
        },
        {
          count: '16',
          time: '1570996020000',
          type: '0015',
        },
        {
          count: '12',
          time: '1570996080000',
          type: '0015',
        },
        {
          count: '9',
          time: '1570996140000',
          type: '0015',
        },
        {
          count: '16',
          time: '1570996200000',
          type: '0015',
        },
        {
          count: '17',
          time: '1570996260000',
          type: '0015',
        },
        {
          count: '20',
          time: '1570996320000',
          type: '0015',
        },
        {
          count: '22',
          time: '1570996380000',
          type: '0015',
        },
        {
          count: '19',
          time: '1570996440000',
          type: '0015',
        },
        {
          count: '18',
          time: '1570996500000',
          type: '0015',
        },
        {
          count: '16',
          time: '1570996560000',
          type: '0015',
        },
        {
          count: '17',
          time: '1570996620000',
          type: '0015',
        },
        {
          count: '16',
          time: '1570996680000',
          type: '0015',
        },
        {
          count: '17',
          time: '1570996740000',
          type: '0015',
        },
        {
          count: '19',
          time: '1570996800000',
          type: '0015',
        },
        {
          count: '21',
          time: '1570996860000',
          type: '0015',
        },
        {
          count: '22',
          time: '1570996920000',
          type: '0015',
        },
        {
          count: '23',
          time: '1570996980000',
          type: '0015',
        },
        {
          count: '20',
          time: '1570997040000',
          type: '0015',
        },
        {
          count: '18',
          time: '1570997100000',
          type: '0015',
        },
        {
          count: '12',
          time: '1570997160000',
          type: '0015',
        },
        {
          count: '12',
          time: '1570997220000',
          type: '0015',
        },
        {
          count: '12',
          time: '1570997280000',
          type: '0015',
        },
        {
          count: '13',
          time: '1570997340000',
          type: '0015',
        },
        {
          count: '14',
          time: '1570997400000',
          type: '0015',
        },
        {
          count: '17',
          time: '1570997460000',
          type: '0015',
        },
        {
          count: '12',
          time: '1570997520000',
          type: '0015',
        },
        {
          count: '19',
          time: '1570997580000',
          type: '0015',
        },
        {
          count: '10',
          time: '1570997640000',
          type: '0015',
        },
        {
          count: '9',
          time: '1570997700000',
          type: '0015',
        },
        {
          count: '3',
          time: '1570997760000',
          type: '0015',
        },
        {
          count: '2',
          time: '1570997820000',
          type: '0015',
        },
        {
          count: '1',
          time: '1570997880000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570997940000',
          type: '0015',
        },
        {
          count: '1',
          time: '1570998000000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998060000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998120000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998180000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998240000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998300000',
          type: '0015',
        },
        {
          count: '1',
          time: '1570998360000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998420000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998480000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998540000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998600000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998660000',
          type: '0015',
        },
        {
          count: '2',
          time: '1570998720000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998780000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998840000',
          type: '0015',
        },
        {
          count: '0',
          time: '1570998900000',
          type: '0015',
        },
      ];
      // G6注册样式
      const LABEL_TAG = 'LABEL_TAG';
      const ADD_TAG = 'ADD_TAG';
      const BALL_TAG = 'BALL_TAG';
      const TOOLTIP_TAG = 'TOOLTIP_TAG';
      // 节点样式的配置参数定义
      const NODE_CONFIG = {
        // 大圆
        nodesRadius: 15, // 点的半径
        colorHealthy: '#1563FF', // 正常颜色
        colorError: '#f75a15', // 非正常颜色
        fadedOpacity: 0.2, // 褪色了的透明度数
        commenOpacity: 1, // 正常的了的透明度数
        // 大院的聚焦状态
        foucsRadusMin: 18, // 聚焦状态的指示圆最小半径
        foucsRadusMax: 25, // 聚焦状态的指示圆最大半径
        foucsColor: 'lightgray',
        foucsOpacity: 0.4,
        foucsWidth: 0.4,
        foucsAnimateDelay: 800,
        // 文本
        labalFontSize: 10,
        labalFontSizeFaded: 5,
        labalFontFillColor: '#fff',
        labalFontStrokeColorHealthy: '#2529e8',
        labalFontStrokeColorError: '#f75a15',
        // 细节信息
        sectionCount: 60, // 圆周细化的区域数
        // 时间指示器
        timeIndicatorstartRadius: 100, // 起始半径
        timeIndicatorEndRadius: 100.5, // 终点半径
        timeIndicatorHoverOffset: 4, // hover时候的终点半径
        timeIndicatorColor: 'lightgray',
        dataSectionColorDataOpacity: 0.7,
        timeIndicatorAnimateDelay: 1200,
        timeIndicatorHoverEnterAnimateDelay: 50,
        timeIndicatorHoverLeaveAnimateDelay: 250,
        // 时间提示
        timeTextOffsetX: 0, // X方面偏移
        timeTextOffsetY: 8, // Y方面偏移
        timeTextRadius: 115, // 文字距离中心的半径
        timeTextFontSize: 9, // 文字大小
        timeTextFontWeight: 1, //粗细
        timeTextColor: 'white', // 颜色
        timeTextHideOpacity: 0, // 隐藏时透明度（已废弃）
        timeTextShowOpacity: 0.7, // 显示透明度
        //数据
        dataSectionStartRaduis: 25, // 数据部分的起始半径
        dataSectionEndRaduis: 80, // 数据部分的终至半径
        dataBarWidthAngle: 2, // 数据的宽度
        dataSectionColorBG: 'white',
        dataSectionColorBGOpacity: 0.2,
        dataSectionColorData: '#A8071A',
        dataSectionColorDataOpacity: 0.7,
        dataSectionAnimateDelay: 1200,
        // 开始位置标记
        makerStartColor: 'lightgray',
        makerStartOpacity: 0.7,
        makerStartPositionX: 0,
        makerStartPositionY: -101,
        makerStartRaduis: 2,
        // 关闭详情（细节）按钮
        closeBtnPositionX: 0,
        // closeBtnPositionY:11,
        closeBtnPositionY: -130,
        closeBtnFontSize: 8,
        closeBtnHoverFontSize: 10,
        closeBtnOpacity: 1,
        closeBtnFontWeight: 1,
        closeBtnCommenColor: 'red',
        closeBtnHoverColor: 'white',
        hoverAnimateTime: 100,
      };
      // 边样式的配置参数定义
      const EDGE_CONFIG = {
        // 虚线设置
        DASH: {
          dashArray: [
            [0, 1],
            [0, 2],
            [1, 2],
            [0, 1, 1, 2],
            [0, 2, 1, 2],
            [1, 2, 1, 2],
            [2, 2, 1, 2],
            [3, 2, 1, 2],
            [4, 2, 1, 2],
          ],
          lineDash: [4, 2, 1, 2],
          interval: 9,
        },
        // 特殊线的横向偏移量，这个值越大，特殊线的弧度越大
        specialArcOffsetX: 30,
        // 正常颜色
        colorOk: '#ccc',
        // 异常颜色
        colorAbnoraml: '#A8071A',
        // 正常线宽
        lineWidthCommon: 1.6,
        // Hover时增长线宽
        lineWidthHoverIncrease: 2,
        // 正常透明度
        opacityCommon: 0.7,
        // faded状态的透明度数
        opacityFaded: 0.1,
        // Hover时透明度
        opacityHover: 0.9,
        // 边上的错误提示半径
        errorTipRadius: 5,
        // 边上的错误外部圆提示颜色
        errorTipBallColor: '#A8071A',
        // 边上的错误文本文字大小
        errorTipTextFontSize: 10,
        // 边上的错误文本颜色
        errorTipTextColor: 'white',
        errorTipTextFontWeight: 600,
        errorTipTextAlign: 'center',
        errorTipTextBaseline: 'center',
        // 流动的指示小球
        // 颜色
        flowingBallColorCommon: 'darkgray',
        flowingBallColorAbnormal: '#A8071A',
        // 半径
        flowingBallRadius: 2,
        // 时间间隔
        flowingTimeInterval: 3000,
      };
      // 调用函数注册样式
      // 节点
      registerNodesStyle();
      // 边
      registerEdgesStyle();
      let data = mockGraphData1.data;
      // 图的尺寸定义 （canvas大小）
      window.graphSize = {
        width: window.innerWidth,
        height: window.innerHeight - 110,
      };
      // 构造G6对象
      const graph = new G6.Graph({
        container: 'mountNode',
        width: window.graphSize.width,
        height: window.graphSize.height,
        autoPaint: true,
        modes: {
          default: [
            'drag-canvas',
            {
              type: 'zoom-canvas',
              sensitivity: 0.8,
            },
            {
              type: 'edge-tooltip',
              formatText: function formatText(model) {
                console.log('model', model);
                const { serviceList = [] } = model;
                if (serviceList.length === 0) {
                  return '链路无异常';
                }
                let text = '异常链路是:<br> ' + serviceList.map(item => item + '<br>');
                return text;
              },
              shouldUpdate: function shouldUpdate(e) {
                return true;
              },
            },
          ],
        },
        fitView: true,
      });
      window.graph = graph;
      // 布局数据
      // 使用dagre
      const layoutedData = layoutByDagre(data);
      // 给数据添加样式
      // 原始数据中没有shape属性
      const styledData = addDataStyle(layoutedData);
      // 读取并绘制
      window.graph.read(styledData);
      /** 更新数据的方法 */
      function updateGraph(data) {
        if (data.nodes.length === 0) {
          window.graph.clear();
        }
        const layoutedData = layoutByDagre(data);
        const styledData = addDataStyle(layoutedData);
        window.graph.read(styledData);
      }
      /** 使用dagre 布局的函数 */
      function layoutByDagre(data) {
        const { nodes, edges } = data;
        const g = new dagre.graphlib.Graph();
        // 给dagre设置方向
        g.setGraph({
          rankdir: 'TB', // 表示方向，可以是TB, BT, LR, RL
        });
        g.setGraph({});
        g.setDefaultEdgeLabel(function() {
          return {};
        });
        let labelNodeMap = {};
        // 给dagre设置点和边
        nodes.forEach(node => {
          g.setNode(node.id, { width: 60, height: 60 });
          labelNodeMap[node.id] = node;
        });
        edges.forEach(function(edge) {
          g.setEdge(edge.source, edge.target);
        });
        // 布局
        dagre.layout(g);
        // 将布局信息，即x和y写入到原对象中
        g.nodes().forEach(v => {
          let originNode = labelNodeMap[v];
          let layoutedNode = g.node(v);
          originNode.x = layoutedNode.x;
          originNode.y = layoutedNode.y;
        });
        // id -> point的映射
        this.nodeIdPointObjMap = labelNodeMap;
        return data;
      }
      /** 构造节点和边样式信息 */
      function addDataStyle(data) {
        // 节点的样式
        data.nodes.forEach(node => {
          node.shape = 'app-node-emergency';
        });
        // 边的样式
        data.edges.forEach(edge => {
          edge.shape = 'ant-edge-emergency';
        });
        return data;
      }

      // ================================
      // 节点样式注册
      function registerNodesStyle() {
        // app-node
        G6.registerNode(
          'app-node-emergency',
          {
            draw: (cfg, group) => {
              const { abnormal } = cfg;
              // 外侧圆形
              const itemBox = group.addShape('circle', {
                attrs: {
                  x: 0,
                  y: 0,
                  r: NODE_CONFIG.nodesRadius,
                  fill: abnormal ? NODE_CONFIG.colorError : NODE_CONFIG.colorHealthy, // 2529e8
                },
              });
              // 文本
              const labelShape = group.addShape('text', {
                attrs: {
                  x: 0,
                  y: 2,
                  text: cfg.app,
                  fontSize: NODE_CONFIG.labalFontSize,
                  fill: NODE_CONFIG.labalFontFillColor,
                  fontWeight: 600,
                  textAlign: 'center',
                  textBaseline: 'center',
                  stroke: abnormal
                    ? NODE_CONFIG.labalFontStrokeColorError
                    : NODE_CONFIG.labalFontStrokeColorHealthy,
                },
                tag: LABEL_TAG,
              });
              return itemBox;
            },

            afterDraw: (cfg, group) => {},

            setState: (name, value, item) => {
              const shape = item.get('keyShape');
              const group = item.get('group');
              switch (name) {
                case 'faded':
                  if (value) {
                    shape.attr('opacity', NODE_CONFIG.fadedOpacity);
                    const labels = group.findAll(item => {
                      return item._cfg.tag && item._cfg.tag === LABEL_TAG;
                    });
                    labels.forEach(lable => {
                      lable.attr('fontSize', NODE_CONFIG.labalFontSizeFaded);
                    });
                  } else {
                    shape.attr('opacity', NODE_CONFIG.commenOpacity);
                    const labels = group.findAll(item => {
                      return item._cfg.tag && item._cfg.tag === LABEL_TAG;
                    });
                    labels.forEach(lable => {
                      lable.attr('fontSize', NODE_CONFIG.labalFontSize);
                    });
                  }
                  break;
                case 'focus':
                  if (value) {
                    // 聚焦状态
                    addFocus(shape, item);
                  } else {
                    // 移除
                    removeAdded(shape, item);
                  }
                  break;
                case 'detailing':
                  if (value) {
                    // 展示细节
                    addDetail(shape, item);
                  } else {
                    // 移除
                    removeAdded(shape, item);
                  }
                  break;
              }
            },
          },
          'circle',
        );
        // 移除菜单函数
        const removeAdded = (shape, item) => {
          const group = shape.getParent();
          // 移除所有添加
          const addEle = group.findAll(item => {
            return item._cfg.tag && item._cfg.tag === ADD_TAG;
          });
          addEle.forEach(ele => {
            // group.removeChild(ele);
            const type = ele._cfg.type;
            if (type === 'fan') {
              // 扇形的时候加上动画
              ele.animate(
                {
                  re: NODE_CONFIG.foucsRadusMin,
                  rs: NODE_CONFIG.foucsRadusMin,
                  repeat: false,
                },
                500,
                'easeLinear',
                () => {
                  group.removeChild(ele);
                },
              );
            } else {
              group.removeChild(ele);
            }
          });
        };
        // 添加细节信息状态
        const addDetail = (shape, item) => {
          if (appDetail.length === 0) {
            // 无详细数据或者数据中没有count字段
            console.log('请求到的详情中没有可绘制字段...');
            return;
          }
          const group = shape.getParent();
          const { _cfg } = item;
          // 计算区域角度
          const perSectionAngle = (2 * Math.PI * (360 / NODE_CONFIG.sectionCount)) / 360;
          const startAngle = -1 * ((2 * Math.PI) / 4); // -90度弧度制的，其实是12点钟方向
          let currentAngle = startAngle;
          const timeIndicatorArr = []; // 时间指示器的图像实例集合
          const timeIndicatorTextArr = []; // 时间指示器的图像实例集合
          const dataSectionBgArr = []; // 数据块背景图像实例集合
          const dataSectionDataArr = []; // 数据块DATA的图像实例集合
          // 绘制数据
          // 分成两个部分
          // 一个是：背景部分 ；另一个是数据部分
          const dataBarWidthAngle = (2 * Math.PI * NODE_CONFIG.dataBarWidthAngle) / 360; // 转化成弧度制
          // 数据映射比例计算出来
          let maxValue = Math.max(...appDetail.map(i => Number(i.count)));
          // console.log('drawmaxValueing maxValue...', maxValue);
          const dataRaduisScale =
            (NODE_CONFIG.dataSectionEndRaduis - NODE_CONFIG.dataSectionStartRaduis) / maxValue;
          // appDetail
          // const { projects }
          // 拿到数据的最大值
          for (let i = 0; i < NODE_CONFIG.sectionCount; i++) {
            // console.log('drawing data ...', i);
            // 数据准备
            const sectionCenterAngle = currentAngle + perSectionAngle / 2;
            const realStartAngle = sectionCenterAngle - dataBarWidthAngle / 2;
            const realEndAngle = sectionCenterAngle + dataBarWidthAngle / 2;
            const thisStartAngle = currentAngle;
            const thisEndAngle = currentAngle + perSectionAngle;
            // 背景部分
            const fanBg = group.addShape('fan', {
              attrs: {
                x: 0,
                y: 0,
                rs: NODE_CONFIG.dataSectionStartRaduis,
                re: NODE_CONFIG.dataSectionEndRaduis,
                startAngle: realStartAngle,
                endAngle: realEndAngle,
                clockwise: false,
                // stroke: NODE_CONFIG.timeIndicatorColor,
                fill: NODE_CONFIG.dataSectionColorBG,
                opacity: NODE_CONFIG.dataSectionColorBGOpacity,
              },
              tag: ADD_TAG,
              cursor: 'pointer',
            });
            dataSectionBgArr.push(fanBg);
            // 数据部分
            // 数据映射
            const odata = Number(appDetail[i].count);
            const dataHeigth = odata * dataRaduisScale;
            const dataRadusEnd = NODE_CONFIG.dataSectionStartRaduis + dataHeigth;
            // console.log('odata', odata );
            // console.log('dataHeigth', dataHeigth );
            // console.log('dataRadusEnd', dataRadusEnd );
            const fanData = group.addShape('fan', {
              attrs: {
                x: 0,
                y: 0,
                rs: NODE_CONFIG.dataSectionStartRaduis,
                re: NODE_CONFIG.dataSectionStartRaduis,
                startAngle: realStartAngle,
                endAngle: realEndAngle,
                clockwise: false,
                // stroke: NODE_CONFIG.timeIndicatorColor,
                fill: NODE_CONFIG.dataSectionColorData,
                opacity: NODE_CONFIG.dataSectionColorDataOpacity,
              },
              tag: ADD_TAG,
              cursor: 'pointer',
            });
            fanData.animate(
              {
                re: dataRadusEnd,
                repeat: false,
              },
              NODE_CONFIG.dataSectionAnimateDelay,
            );

            // --------------------------不好用就注释
            // 监听
            const { time, count } = appDetail[i];
            [fanData, fanBg].forEach(item => {
              item.on('mouseenter', function(evt) {
                const fanTimer = timeIndicatorArr[i];
                // 自身动画
                fanTimer.animate(
                  {
                    rs:
                      NODE_CONFIG.timeIndicatorstartRadius -
                      NODE_CONFIG.timeIndicatorHoverOffset / 2,
                    re:
                      NODE_CONFIG.timeIndicatorEndRadius + NODE_CONFIG.timeIndicatorHoverOffset / 2,
                    repeat: false,
                  },
                  NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay,
                );
                // 纵向指示器的变化
                fanBg.animate(
                  {
                    startAngle: thisStartAngle,
                    endAngle: thisEndAngle,
                    re:
                      NODE_CONFIG.timeIndicatorstartRadius -
                      NODE_CONFIG.timeIndicatorHoverOffset / 2,
                    repeat: false,
                  },
                  NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay,
                );
                // 提示文本
                // 准备文本信息
                // const { time, count } = appDetail[i];
                const timeString = moment(Number(time)).format('YYYY-MM-DD HH:mm:ss');
                const countString = `错误量 : ${count}`;
                const textTimeX = NODE_CONFIG.timeTextRadius * Math.cos(sectionCenterAngle);
                const textTimeY = NODE_CONFIG.timeTextRadius * Math.sin(sectionCenterAngle);
                let textAlign = 'left';
                if (textTimeX < 0) {
                  textAlign = 'right';
                }
                // add 文本
                // 用这种在on事件中addShape的方式比opacity改变的方式好在哪里？
                // 这样避免一个opacity为0但实际元素存在，干扰操作的问题
                group.addShape('text', {
                  attrs: {
                    x: textTimeX + NODE_CONFIG.timeTextOffsetX,
                    y: textTimeY + NODE_CONFIG.timeTextOffsetY,
                    text: `${timeString}\n${countString}`,
                    fontSize: NODE_CONFIG.timeTextFontSize,
                    fill: NODE_CONFIG.timeTextColor,
                    opacity: NODE_CONFIG.timeTextShowOpacity,
                    fontWeight: NODE_CONFIG.timeTextFontWeight,
                    textAlign: textAlign,
                    textBaseline: 'center',
                    // stroke: 'white',
                  },
                  tag: TOOLTIP_TAG,
                });
              });
              item.on('mouseleave', function(evt) {
                const fanTimer = timeIndicatorArr[i];
                // 自身动画
                fanTimer.animate(
                  {
                    rs: NODE_CONFIG.timeIndicatorstartRadius,
                    re: NODE_CONFIG.timeIndicatorEndRadius,
                    repeat: false,
                  },
                  NODE_CONFIG.timeIndicatorHoverLeaveAnimateDelay,
                );
                // 径向指示动画
                fanBg.animate(
                  {
                    startAngle: realStartAngle,
                    endAngle: realEndAngle,
                    re: NODE_CONFIG.dataSectionEndRaduis,
                    repeat: false,
                  },
                  NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay,
                );
                // 移除文字
                const tooltips = group.findAll(item => {
                  return item._cfg.tag && item._cfg.tag === TOOLTIP_TAG;
                });
                tooltips.forEach(tooltip => {
                  tooltip.remove();
                });
              });
            });
            // --------------------------不好用就注释 <--
            dataSectionDataArr.push(fanData);
            currentAngle += perSectionAngle;
          }
          // 绘制 时间指示器
          for (let i = 0; i < NODE_CONFIG.sectionCount; i++) {
            const odata = appDetail[i];
            // 数据准备
            const bkGraphObj = dataSectionBgArr[i];
            const dataGraphObj = dataSectionDataArr[i];
            const thisStartAngle = currentAngle;
            const thisEndAngle = currentAngle + perSectionAngle;
            const sectionCenterAngle = currentAngle + perSectionAngle / 2;
            const realStartAngle = sectionCenterAngle - dataBarWidthAngle / 2;
            const realEndAngle = sectionCenterAngle + dataBarWidthAngle / 2;
            // 小扇形
            const fanTimer = group.addShape('fan', {
              attrs: {
                x: 0,
                y: 0,
                rs: NODE_CONFIG.timeIndicatorstartRadius,
                re: NODE_CONFIG.timeIndicatorEndRadius,
                startAngle: currentAngle,
                endAngle: currentAngle,
                clockwise: false,
                // stroke: NODE_CONFIG.timeIndicatorColor,
                fill: NODE_CONFIG.timeIndicatorColor,
                opacity: NODE_CONFIG.dataSectionColorDataOpacity,
              },
              tag: ADD_TAG,
              cursor: 'pointer',
            });
            // 小扇形的动画
            fanTimer.animate(
              {
                endAngle: currentAngle + perSectionAngle,
                repeat: false,
              },
              NODE_CONFIG.timeIndicatorAnimateDelay,
            );
            // 监听交互事件
            fanTimer.on('mouseenter', function(evt) {
              // 自身动画
              fanTimer.animate(
                {
                  rs:
                    NODE_CONFIG.timeIndicatorstartRadius - NODE_CONFIG.timeIndicatorHoverOffset / 2,
                  re: NODE_CONFIG.timeIndicatorEndRadius + NODE_CONFIG.timeIndicatorHoverOffset / 2,
                  repeat: false,
                },
                NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay,
              );
              // 纵向指示器的变化
              bkGraphObj.animate(
                {
                  startAngle: thisStartAngle,
                  endAngle: thisEndAngle,
                  re:
                    NODE_CONFIG.timeIndicatorstartRadius - NODE_CONFIG.timeIndicatorHoverOffset / 2,
                  repeat: false,
                },
                NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay,
              );
              // 提示文本
              // 准备文本信息
              const { time, count } = odata;
              const timeString = moment(Number(time)).format('YYYY-MM-DD HH:mm:ss');
              const countString = `错误量 : ${count}`;
              const textTimeX = NODE_CONFIG.timeTextRadius * Math.cos(sectionCenterAngle);
              const textTimeY = NODE_CONFIG.timeTextRadius * Math.sin(sectionCenterAngle);
              let textAlign = 'left';
              if (textTimeX < 0) {
                textAlign = 'right';
              }
              // add 文本
              // 用这种在on事件中addShape的方式比opacity改变的方式好在哪里？
              // 这样避免一个opacity为0但实际元素存在，干扰操作的问题
              group.addShape('text', {
                attrs: {
                  x: textTimeX + NODE_CONFIG.timeTextOffsetX,
                  y: textTimeY + NODE_CONFIG.timeTextOffsetY,
                  text: `${timeString}\n${countString}`,
                  fontSize: NODE_CONFIG.timeTextFontSize,
                  fill: NODE_CONFIG.timeTextColor,
                  opacity: NODE_CONFIG.timeTextShowOpacity,
                  fontWeight: NODE_CONFIG.timeTextFontWeight,
                  textAlign: textAlign,
                  textBaseline: 'center',
                  // stroke: 'white',
                },
                tag: TOOLTIP_TAG,
              });
            });
            // 鼠标移开的事件监听
            fanTimer.on('mouseleave', function(evt) {
              // 自身动画
              fanTimer.animate(
                {
                  rs: NODE_CONFIG.timeIndicatorstartRadius,
                  re: NODE_CONFIG.timeIndicatorEndRadius,
                  repeat: false,
                },
                NODE_CONFIG.timeIndicatorHoverLeaveAnimateDelay,
              );
              // 径向指示动画
              bkGraphObj.animate(
                {
                  startAngle: realStartAngle,
                  endAngle: realEndAngle,
                  re: NODE_CONFIG.dataSectionEndRaduis,
                  repeat: false,
                },
                NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay,
              );
              // 移除文字
              const tooltips = group.findAll(item => {
                return item._cfg.tag && item._cfg.tag === TOOLTIP_TAG;
              });
              tooltips.forEach(tooltip => {
                tooltip.remove();
              });
            });
            // 图形引用入栈
            timeIndicatorArr.push(fanTimer);
            currentAngle += perSectionAngle;
          }
          // 绘制“关闭详情”提示文字
          const closeText = '关闭扩展';
          const closeBtn = group.addShape('text', {
            attrs: {
              x: NODE_CONFIG.closeBtnPositionX,
              y: NODE_CONFIG.closeBtnPositionY,
              text: closeText,
              fontSize: NODE_CONFIG.closeBtnFontSize,
              fill: NODE_CONFIG.closeBtnCommenColor,
              opacity: NODE_CONFIG.closeBtnOpacity,
              fontWeight: NODE_CONFIG.closeBtnCommenColor,
              textAlign: 'center',
              textBaseline: 'center',
              cursor: 'pointer',
              // stroke: 'white',
            },
            tag: ADD_TAG,
          });
          closeBtn.on('click', () => {
            // this.graphWapper.emit('close_detail', _cfg.id);
            closeDetail();
          });
          closeBtn.on('mouseenter', () => {
            closeBtn.animate(
              {
                fill: NODE_CONFIG.closeBtnHoverColor,
                stroke: NODE_CONFIG.closeBtnHoverColor,
                fontSize: NODE_CONFIG.closeBtnHoverFontSize,
                repeat: false,
              },
              NODE_CONFIG.hoverAnimateTime,
            );
          });

          closeBtn.on('mouseleave', () => {
            closeBtn.animate(
              {
                fill: NODE_CONFIG.closeBtnCommenColor,
                fontSize: NODE_CONFIG.closeBtnFontSize,
                stroke: null,
                repeat: false,
              },
              NODE_CONFIG.hoverAnimateTime,
            );
          });
          // 绘制开始方向提示
          const marker = group.addShape('marker', {
            attrs: {
              // x: NODE_CONFIG.makerStartPositionX,
              // y: NODE_CONFIG.makerStartPositionY,
              x: 0,
              y: 0,
              r: NODE_CONFIG.makerStartRaduis,
              fill: NODE_CONFIG.makerStartColor,
              symbol: 'triangle',
              opacity: NODE_CONFIG.makerStartOpacity,
            },
            tag: ADD_TAG,
          });
          // 偏移和旋转这个marker
          marker.rotate(-Math.PI / 6);
          marker.translate(NODE_CONFIG.makerStartPositionX, NODE_CONFIG.makerStartPositionY);
        };
        // 添加Focus的状态
        const addFocus = (shape, item) => {
          const group = shape.getParent();
          const { _cfg } = item;

          const foucsRing = group.addShape('circle', {
            attrs: {
              x: 0,
              y: 0,
              r: NODE_CONFIG.foucsRadusMax,
              // fill: abnormal ? NODE_CONFIG.colorError: NODE_CONFIG.colorHealthy, // 2529e8
              stroke: NODE_CONFIG.foucsColor,
              lineWidth: NODE_CONFIG.foucsWidth,
              opacity: NODE_CONFIG.foucsOpacity,
            },
            tag: ADD_TAG,
          });
          foucsRing.animate(
            {
              r: NODE_CONFIG.foucsRadusMin,
              repeat: true,
            },
            NODE_CONFIG.foucsAnimateDelay,
          );
        };
      }
      // ================================
      // 边样式注册
      function registerEdgesStyle() {
        if (!G6) {
          return;
        }
        const dashArray = EDGE_CONFIG.DASH.dashArray;
        const lineDash = EDGE_CONFIG.DASH.lineDash;
        const interval = EDGE_CONFIG.DASH.interval;
        G6.registerEdge('ant-edge-emergency', {
          draw: function draw(cfg, group) {
            const startPoint = cfg.startPoint;
            const endPoint = cfg.endPoint;
            const centerPoint = {
              x: startPoint.x + (endPoint.x - startPoint.x) / 2,
              y: startPoint.y + (endPoint.y - startPoint.y) / 2,
            };
            const controlPoint = {
              x: startPoint.x,
              y: (startPoint.y + centerPoint.y) / 2,
            };
            let specialArcOffsetX = EDGE_CONFIG.specialArcOffsetX;
            const lineColor = cfg.abnormal ? EDGE_CONFIG.colorAbnoraml : EDGE_CONFIG.colorOk;

            let path;
            if (cfg.isSpecial) {
              if (endPoint.y < startPoint.y) {
                specialArcOffsetX *= -1;
              }
              path = group.addShape('path', {
                attrs: {
                  path: [
                    ['M', startPoint.x, startPoint.y],
                    ['Q', centerPoint.x - specialArcOffsetX, centerPoint.y, endPoint.x, endPoint.y],
                  ],
                  stroke: lineColor,
                  lineWidth: EDGE_CONFIG.lineWidthCommon,
                  opacity: EDGE_CONFIG.opacityCommon,
                  endArrow: {
                    path: 'M 4,0 L -4,-4 L -4,4 Z',
                    d: 5,
                  },
                },
              });
            } else {
              path = group.addShape('path', {
                attrs: {
                  path: [
                    ['M', startPoint.x, startPoint.y],
                    ['Q', controlPoint.x, controlPoint.y, centerPoint.x, centerPoint.y],
                    ['T', endPoint.x, endPoint.y],
                  ],
                  stroke: lineColor,
                  lineWidth: EDGE_CONFIG.lineWidthCommon,
                  opacity: EDGE_CONFIG.opacityCommon,
                  endArrow: {
                    path: 'M 4,0 L -4,-4 L -4,4 Z',
                    d: 5,
                  },
                },
              });
            }
            // 不正常的时候需要显示提示
            if (cfg.abnormal) {
              //
              let thePoint = {}; // 要显示的位置
              if (cfg.isSpecial) {
                thePoint = {
                  x: centerPoint.x - specialArcOffsetX / 2,
                  y: centerPoint.y,
                };
              } else {
                thePoint = {
                  x: centerPoint.x,
                  y: centerPoint.y,
                };
              }
              const itemBox = group.addShape('circle', {
                attrs: {
                  x: thePoint.x,
                  y: thePoint.y,
                  r: EDGE_CONFIG.errorTipRadius,
                  fill: EDGE_CONFIG.errorTipBallColor, // 2529e8
                  opacity: EDGE_CONFIG.opacityCommon,
                },
                tag: BALL_TAG,
              });
              // 文本
              const labelShape = group.addShape('text', {
                attrs: {
                  x: thePoint.x,
                  y: thePoint.y + 2,
                  text: cfg.serviceList.length,
                  fontSize: EDGE_CONFIG.errorTipTextFontSize,
                  fill: EDGE_CONFIG.errorTipTextColor,
                  fontWeight: EDGE_CONFIG.errorTipTextFontWeight,
                  textAlign: EDGE_CONFIG.errorTipTextAlign,
                  textBaseline: EDGE_CONFIG.errorTipTextBaseline,
                  opacity: EDGE_CONFIG.opacityCommon,
                  // stroke: !isHealthy ? NODE_CONFIG.labalFontStrokeColorError : NODE_CONFIG.labalFontStrokeColorHealthy,
                },
              });
            }

            return path;
          },
          setState(name, value, item) {
            const shape = item.get('keyShape');
            const group = shape.getParent();
            switch (name) {
              case 'faded':
                if (value) {
                  const opacity = EDGE_CONFIG.opacityFaded;
                  shape.attr('opacity', opacity);
                  const balls = group.findAll(item => {
                    return item._cfg.tag && item._cfg.tag === BALL_TAG;
                  });
                  balls.forEach(ball => {
                    ball.attr('opacity', opacity);
                  });
                } else {
                  const opacity = EDGE_CONFIG.opacityCommon;
                  shape.attr('opacity', opacity);
                  const balls = group.findAll(item => {
                    return item._cfg.tag && item._cfg.tag === BALL_TAG;
                  });
                  balls.forEach(ball => {
                    ball.attr('opacity', opacity);
                  });
                }
                break;
              case 'hoverStyle':
                const { _cfg } = item;
                const { originStyle } = _cfg;
                const { lineWidth } = originStyle;
                const increaseWidth = EDGE_CONFIG.lineWidthHoverIncrease;
                // console.log("_cfg", _cfg);
                if (value) {
                  shape.attr({
                    lineWidth: lineWidth + increaseWidth,
                    opacity: EDGE_CONFIG.opacityHover,
                  });
                } else {
                  shape.attr({
                    lineWidth,
                    opacity: EDGE_CONFIG.opacityCommon,
                  });
                }
                break;
            }
          },

          afterDraw(cfg, group) {
            const shape = group.get('children')[0];
            const startPoint = shape.getPoint(0);
            const circle = group.addShape('circle', {
              attrs: {
                x: startPoint.x,
                y: startPoint.y,
                fill: cfg.abnormal
                  ? EDGE_CONFIG.flowingBallColorAbnormal
                  : EDGE_CONFIG.flowingBallColorCommon,
                r: 2,
              },
              tag: BALL_TAG,
            });
            circle.animate(
              {
                onFrame(ratio) {
                  const tmpPoint = shape.getPoint(ratio);
                  return {
                    x: tmpPoint.x,
                    y: tmpPoint.y,
                  };
                },
                repeat: true,
              },
              EDGE_CONFIG.flowingTimeInterval,
            );
            const length = shape.getTotalLength(); // G 增加了 totalLength 的接口
            let totalArray = [];
            for (var i = 0; i < length; i += interval) {
              totalArray = totalArray.concat(lineDash);
            }
            let index = 0;
            shape.animate(
              {
                onFrame(ratio) {
                  const cfg = {
                    lineDash: dashArray[index].concat(totalArray),
                  };
                  index = (index + 1) % interval;
                  return cfg;
                },
                repeat: true,
              },
              EDGE_CONFIG.flowingTimeInterval,
            );
          },
        });
      }
      function focusOnNodePoint(id, zoomViewScale) {
        const { width, height } = window.graphSize;
        const item = window.graph.findById(id);
        const model = item.get('model');
        const matrix = window.graph.get('group').getMatrix();
        const x = model.x * matrix[0] + matrix[6];
        const y = model.y * matrix[4] + matrix[7];
        window.graph.translate(width / 2 - x, height / 2 - y);
        if (zoomViewScale) {
          zoomByCenter(zoomViewScale);
        }
      }
      function zoomByCenter(scale) {
        const { width, height } = this.graphSize;
        const centerPosition = { x: width / 2, y: height / 2 };
        window.graph.zoom(scale / window.graph.getZoom(), centerPosition);
      }
      function fadeAllButOne(besideNodeId) {
        window.graph.getNodes().forEach(node => {
          const nodeid = node._cfg.id;
          if (besideNodeId !== nodeid) {
            window.graph.setItemState(nodeid, 'faded', true);
          }
        });
        window.graph.getEdges().forEach(edge => {
          const edgeid = edge._cfg.id;
          window.graph.setItemState(edgeid, 'faded', true);
        });
      }
      function showInitialGraph() {
        window.graph.getNodes().forEach(node => {
          const nodeid = node._cfg.id;
          window.graph.setItemState(nodeid, 'faded', false);
        });
        window.graph.getEdges().forEach(edge => {
          const edgeid = edge._cfg.id;
          window.graph.showItem(edgeid);
          window.graph.setItemState(edgeid, 'faded', false);
        });
      }
      // 分析按钮的监听回调
      document.getElementById('btn_error').addEventListener('click', () => {
        // 模拟系统详情
        document.getElementById('infoDetail').innerHTML = `
      <div style="color: white;">
        <p>source_system</p>
        <hr >
        <p>系统owner：cjj</p>
        <p>系统等级：Level High</p>
        <hr  >
        <p>问题原因是：XXX</p>
        <p>最近趋势图：YYY</p>
        <p>解决方案是：ZZZ</p>
      </div>
      `;
        // 聚焦到节点
        fadeAllButOne('source_system');
        focusOnNodePoint('source_system', 2);
        window.graph.setItemState('source_system', 'detailing', true);
      });
      function closeDetail() {
        window.graph.setItemState('source_system', 'detailing', false);
        showInitialGraph();
        document.getElementById('infoDetail').innerHTML = '';
      }
      // =========================
      // 时间轴部分
      const timeMockData = [
        { time: '23:07', value: 0, date: '2019-10-14', timeStamp: 1571065620000 },
        { time: '23:08', value: 0, date: '2019-10-14', timeStamp: 1571065680000 },
        { time: '23:09', value: 0, date: '2019-10-14', timeStamp: 1571065740000 },
        { time: '23:10', value: 0, date: '2019-10-14', timeStamp: 1571065800000 },
        { time: '23:11', value: 0, date: '2019-10-14', timeStamp: 1571065860000 },
        { time: '23:12', value: 0, date: '2019-10-14', timeStamp: 1571065920000 },
        { time: '23:13', value: 0, date: '2019-10-14', timeStamp: 1571065980000 },
        { time: '23:14', value: 0, date: '2019-10-14', timeStamp: 1571066040000 },
        { time: '23:15', value: 0, date: '2019-10-14', timeStamp: 1571066100000 },
        { time: '23:16', value: 0, date: '2019-10-14', timeStamp: 1571066160000 },
        { time: '23:17', value: 0, date: '2019-10-14', timeStamp: 1571066220000 },
        { time: '23:18', value: 0, date: '2019-10-14', timeStamp: 1571066280000 },
        { time: '23:19', value: 0, date: '2019-10-14', timeStamp: 1571066340000 },
        { time: '23:20', value: 0, date: '2019-10-14', timeStamp: 1571066400000 },
        { time: '23:21', value: 0, date: '2019-10-14', timeStamp: 1571066460000 },
        { time: '23:22', value: 0, date: '2019-10-14', timeStamp: 1571066520000 },
        { time: '23:23', value: 0, date: '2019-10-14', timeStamp: 1571066580000 },
        { time: '23:24', value: 0, date: '2019-10-14', timeStamp: 1571066640000 },
        { time: '23:25', value: 0, date: '2019-10-14', timeStamp: 1571066700000 },
        { time: '23:26', value: 0, date: '2019-10-14', timeStamp: 1571066760000 },
        { time: '23:27', value: 0, date: '2019-10-14', timeStamp: 1571066820000 },
        { time: '23:28', value: 0, date: '2019-10-14', timeStamp: 1571066880000 },
        { time: '23:29', value: 0, date: '2019-10-14', timeStamp: 1571066940000 },
        { time: '23:30', value: 0, date: '2019-10-14', timeStamp: 1571067000000 },
        { time: '23:31', value: 0, date: '2019-10-14', timeStamp: 1571067060000 },
        { time: '23:32', value: 0, date: '2019-10-14', timeStamp: 1571067120000 },
        { time: '23:33', value: 0, date: '2019-10-14', timeStamp: 1571067180000 },
        { time: '23:34', value: 0, date: '2019-10-14', timeStamp: 1571067240000 },
        { time: '23:35', value: 0, date: '2019-10-14', timeStamp: 1571067300000 },
        { time: '23:36', value: 0, date: '2019-10-14', timeStamp: 1571067360000 },
        { time: '23:37', value: 2, date: '2019-10-14', timeStamp: 1571067420000 },
        { time: '23:38', value: 11, date: '2019-10-14', timeStamp: 1571067480000 },
        { time: '23:39', value: 11, date: '2019-10-14', timeStamp: 1571067540000 },
        { time: '23:40', value: 11, date: '2019-10-14', timeStamp: 1571067600000 },
        { time: '23:41', value: 10, date: '2019-10-14', timeStamp: 1571067660000 },
        { time: '23:42', value: 0, date: '2019-10-14', timeStamp: 1571067720000 },
        { time: '23:43', value: 0, date: '2019-10-14', timeStamp: 1571067780000 },
        { time: '23:44', value: 0, date: '2019-10-14', timeStamp: 1571067840000 },
        { time: '23:45', value: 0, date: '2019-10-14', timeStamp: 1571067900000 },
        { time: '23:46', value: 0, date: '2019-10-14', timeStamp: 1571067960000 },
        { time: '23:47', value: 0, date: '2019-10-14', timeStamp: 1571068020000 },
        { time: '23:48', value: 0, date: '2019-10-14', timeStamp: 1571068080000 },
        { time: '23:49', value: 0, date: '2019-10-14', timeStamp: 1571068140000 },
        { time: '23:50', value: 0, date: '2019-10-14', timeStamp: 1571068200000 },
        { time: '23:51', value: 0, date: '2019-10-14', timeStamp: 1571068260000 },
        { time: '23:52', value: 0, date: '2019-10-14', timeStamp: 1571068320000 },
        { time: '23:53', value: 5, date: '2019-10-14', timeStamp: 1571068380000 },
        { time: '23:54', value: 5, date: '2019-10-14', timeStamp: 1571068440000 },
        { time: '23:55', value: 0, date: '2019-10-14', timeStamp: 1571068500000 },
        { time: '23:56', value: 0, date: '2019-10-14', timeStamp: 1571068560000 },
        { time: '23:57', value: 0, date: '2019-10-14', timeStamp: 1571068620000 },
        { time: '23:58', value: 0, date: '2019-10-14', timeStamp: 1571068680000 },
        { time: '23:59', value: 0, date: '2019-10-14', timeStamp: 1571068740000 },
        { time: '00:00', value: 0, date: '2019-10-15', timeStamp: 1571068800000 },
        { time: '00:01', value: 0, date: '2019-10-15', timeStamp: 1571068860000 },
        { time: '00:02', value: 0, date: '2019-10-15', timeStamp: 1571068920000 },
        { time: '00:03', value: 0, date: '2019-10-15', timeStamp: 1571068980000 },
        { time: '00:04', value: 0, date: '2019-10-15', timeStamp: 1571069040000 },
        { time: '00:05', value: 0, date: '2019-10-15', timeStamp: 1571069100000 },
        { time: '00:06', value: 0, date: '2019-10-15', timeStamp: 1571069160000 },
        { time: '00:07', value: 0, date: '2019-10-15', timeStamp: 1571069220000 },
      ];
      const TIME_CONTROLLER_CONFIG = {
        // 模块整体宽度
        width: 640,
        // 模块整体高
        height: 64,
        // padding  边距
        padding: {
          top: 4,
          bottom: 8,
          left: 8,
          right: 8,
        },
        // 控制按钮大小
        controlButtonR: 10,
        // 时间轴轴线宽
        axisLineWidth: 2,
        // 柱子区域的水平间距
        barSectionPaddingH: 8,
        // 柱子间距
        barPadding: 2,
        // icon 线的水平位置偏移
        iconXOffset: 2,
        // icon 线的纵偏移
        iconYOffset: 5,
        // icon 的线
        iconStrokeWidth: 1,
        // 各种颜色定义
        COLORS: {
          // x轴
          axis: '#597EF7',
          // 控制按钮
          controlBtn: '#69C0FF',
          // 按钮上icon的颜色
          iconInBtn: '#080C16',
        },
      };
      const {
        width,
        height,
        padding,
        controlButtonR,
        axisLineWidth,
        barSectionPaddingH,
        barPadding,
        iconXOffset,
        iconYOffset,
        iconStrokeWidth,
        COLORS,
      } = TIME_CONTROLLER_CONFIG;
      // 时间轴控制按钮位置计算
      const leftControlButtonPosition = {
        x: padding.left + controlButtonR,
        y: height - padding.bottom - controlButtonR,
      };
      const rightControlButtonPosition = {
        x: width - padding.right - controlButtonR,
        y: height - padding.bottom - controlButtonR,
      };
      const barSectionHeight =
        height - padding.top - padding.bottom - axisLineWidth - controlButtonR;
      const barSectionWidth =
        width - padding.left - padding.right - controlButtonR * 4 - barSectionPaddingH * 2;
      // svg
      const svg = d3
        .select('#timeControllerContainer')
        .append('svg')
        .attr('height', height)
        .attr('width', width);
      window.svg = svg;
      // 画轴
      svg
        .append('line')
        .attr('x1', leftControlButtonPosition.x)
        .attr('y1', leftControlButtonPosition.y)
        .attr('x2', rightControlButtonPosition.x)
        .attr('y2', rightControlButtonPosition.y)
        .attr('stroke-width', axisLineWidth)
        .attr('stroke', COLORS.axis);
      // 控制器：left
      const leftControlG = svg.append('g');
      leftControlG
        .append('circle')
        .attr('id', 'circleBtn')
        .attr('r', controlButtonR)
        .attr('cx', leftControlButtonPosition.x)
        .attr('cy', leftControlButtonPosition.y)
        .attr('fill', COLORS.controlBtn)
        .style('cursor', 'pointer');
      leftControlG
        .append('line')
        .attr('x1', leftControlButtonPosition.x + iconXOffset)
        .attr('y1', leftControlButtonPosition.y - iconYOffset)
        .attr('x2', leftControlButtonPosition.x - iconXOffset)
        .attr('y2', leftControlButtonPosition.y)
        .attr('stroke-width', iconStrokeWidth)
        .attr('stroke', COLORS.iconInBtn)
        .style('cursor', 'pointer');
      leftControlG
        .append('line')
        .attr('x1', leftControlButtonPosition.x - iconXOffset)
        .attr('y1', leftControlButtonPosition.y)
        .attr('x2', leftControlButtonPosition.x + iconXOffset)
        .attr('y2', leftControlButtonPosition.y + iconYOffset)
        .attr('stroke-width', iconStrokeWidth)
        .attr('stroke', COLORS.iconInBtn)
        .style('cursor', 'pointer');
      // 左边button的点击回调
      leftControlG.on('click', function(d) {
        // console.log('d3.select(this)', d3.select('#circleBtn').transition())
        // d3.select(this)
        //   .select('#circleBtn')
        //   .transition()      //要实现单元素连续动画就直接加在后面
        //   // .duration(100)
        //   .attr('r', controlButtonR + 2)
        //   .transition()      //要实现单元素连续动画就直接加在后面
        //   .duration(100)
        //   .attr('r', controlButtonR );
        // 点击回调
        handleClickBtn('back');
      });
      // 控制器：right
      const rightControlG = svg.append('g');
      rightControlG
        .append('circle')
        .attr('r', controlButtonR)
        .attr('cx', rightControlButtonPosition.x)
        .attr('cy', rightControlButtonPosition.y)
        .attr('fill', COLORS.controlBtn)
        .style('cursor', 'pointer');
      rightControlG
        .append('line')
        .attr('x1', rightControlButtonPosition.x - iconXOffset)
        .attr('y1', rightControlButtonPosition.y - iconYOffset)
        .attr('x2', rightControlButtonPosition.x + iconXOffset)
        .attr('y2', rightControlButtonPosition.y)
        .attr('stroke-width', iconStrokeWidth)
        .attr('stroke', COLORS.iconInBtn)
        .style('cursor', 'pointer');
      rightControlG
        .append('line')
        .attr('x1', rightControlButtonPosition.x + iconXOffset)
        .attr('y1', rightControlButtonPosition.y)
        .attr('x2', rightControlButtonPosition.x - iconXOffset)
        .attr('y2', rightControlButtonPosition.y + iconYOffset)
        .attr('stroke-width', iconStrokeWidth)
        .attr('stroke', COLORS.iconInBtn)
        .style('cursor', 'pointer');
      // 右边button的点击回调
      rightControlG.on('click', d => {
        // 点击回调
        handleClickBtn('front');
      });
      drawData(timeMockData);
      function drawData(data) {
        const lastIndex = data.length - 1;
        const svg = window.svg;
        const yScale = barSectionHeight / d3.max(data.map(i => i.value));
        const barsGroup = svg.append('g').attr('id', 'oneBarGroup');
        const oneBarGroup = barsGroup
          .selectAll('rect')
          .data(data)
          .enter()
          .append('g');
        // 背景
        oneBarGroup
          .append('rect')
          .attr('id', 'rect_bg')
          .attr('x', (d, i) => {
            return (
              i * (barSectionWidth / data.length) +
              padding.left +
              controlButtonR * 2 +
              barSectionPaddingH
            );
          })
          .attr('y', (d, i) => {
            return padding.top;
          })
          .attr('width', barSectionWidth / data.length - barPadding)
          .attr('height', barSectionHeight)
          .attr('fill', '#597EF7')
          .attr('opacity', 0.2)
          .attr('stroke', 'white')
          .attr('stroke-width', (d, i) => {
            return i === lastIndex ? 2 : 0;
          });

        // bar
        oneBarGroup
          .append('rect')
          .attr('id', 'rect')
          .attr('x', (d, i) => {
            return (
              i * (barSectionWidth / data.length) +
              padding.left +
              controlButtonR * 2 +
              barSectionPaddingH
            );
          })
          .attr('y', (d, i) => {
            const maybeValue = barSectionHeight - d.value * yScale + padding.top;
            const yValue = isNaN(maybeValue) ? 0 : maybeValue;
            return yValue;
          })
          .attr('width', barSectionWidth / data.length - barPadding)
          .attr('height', (d, i) => {
            const maybeValue = d.value * yScale;
            const yValue = isNaN(maybeValue) ? 0 : maybeValue;
            return yValue;
          })
          .attr('fill', '#69C0FF')
          .attr('opacity', 0.75)
          .attr('stroke', 'white')
          .attr('stroke-width', (d, i) => {
            return i === lastIndex ? 2 : 0;
          });
        // 添加event
        oneBarGroup
          .style('cursor', 'pointer')
          .on('click', function(d) {
            let selectIdTag = '#rect';
            if (d.value === 0) {
              selectIdTag = '#rect_bg';
            }
            // 更新选择状态
            d3.selectAll('#rect_bg').style('stroke-width', 0);
            d3.selectAll('#rect').style('stroke-width', 0);
            //
            const selectRect = d3.select(this).select(selectIdTag);
            selectRect.style('stroke-width', 2);
            //
            clickRectQuery(d);
          })
          .on('mouseover', function(d) {
            const selectRect = d3.select(this).select('#rect');
            selectRect.style('opacity', 1);
            // console.log(d3.select(this).select('#rect').nodes())
            // console.log(d3.event)
            const { offsetX, offsetY } = d3.event;
            setTooltipState({
              isTooltipVisable: true,
              toolTipData: selectRect.nodes()[0].__data__,
              relativePosition: {
                x: offsetX,
                y: offsetY,
              },
            });
          })
          .on('mouseout', function(d) {
            d3.select(this)
              .select('#rect')
              .style('opacity', 0.75);
            setTooltipState({
              isTooltipVisable: false,
            });
          });
        // text
        const textAxisGroup = svg.append('g').attr('id', 'textAxisGroup');
        textAxisGroup
          .selectAll('rect')
          .data(data)
          .enter()
          .append('g')
          .append('text')
          .attr('fill', 'white')
          .attr('font-size', '12px')
          .attr('text-anchor', 'middle')
          .attr('x', function(d, i) {
            //与矩形的X坐标一样
            return (
              i * (barSectionWidth / data.length) +
              padding.left +
              controlButtonR * 2 +
              barSectionPaddingH +
              8
            );
          })
          .attr('y', function(d) {
            return barSectionHeight + 20 + padding.top;
          })
          .text(function(d, i) {
            //要显示的文字内容
            if (i % 6 === 0) {
              return d.time;
            }
            return '';
          });
        // mouse enter的回调
        const setTooltipState = ({ isTooltipVisable, toolTipData, relativePosition }) => {};
        const clickRectQuery = d => {
          updateGraph(mockGraphData2.data);
        };
      }
    </script>
  </body>
</html>
